#!/usr/bin/perl -I /opt/hp/nv/lib/perl

## (C) Copyright 2008-2011 Hewlett-Packard Development Company, L.P.
##
## $Id: lunmgr 15188 2012-02-17 00:08:00Z cheng-xin.cai@hp.com $

use strict;
use warnings;

use POSIX;
use Fcntl ":flock";
use Switch;
use File::Basename;
use Cwd 'abs_path';
use Getopt::Long;
use Sys::Syslog qw(:standard :macros);
use Data::Dumper qw( Dumper );
use Net::Telnet;
use XML::Twig;
use Term::ReadKey;
use Tie::File; # For easily modifying single line(s) in the device table
use Sys::Hostname;
use threads;
use threads::shared;

use File::LockDir;
use Lunmgr::Globals;
use Lunmgr::Device;
use MSA::Enclosure;
use BladesEnc::OA;

use lib '/home/caiche/lib/perl';
use TimeCheck;

my $gs_lsscsi_cmd;
my $gs_scsiid_cmd;
my $gs_multipath_cmd;
my $gs_sg_inq_cmd;
my $gs_mount_cmd;
my $gs_umount_cmd;
my $gs_pvscan_cmd;
my $gs_qlrescan_cmd;
my $gs_smartctl_cmd;
my $gs_fuser_cmd;
my $gs_scli_cmd;
my $gs_ps_cmd;
my $gs_pdsh_cmd;
my $gs_fcpass;
my $gs_fdisk_cmd;
my @ga_alldisks;
my @ga_allMSAs;
my @ga_all_vdisk_twigs; # An array of all "show vdisks" output for attached MSAs
my @ga_all_disk_twigs; # An array of all "show vdisks" output for attached MSAs
my $gs_version;
my $gb_trace_function_calls = TRUE; # Set to TRUE to trace function calls
my $gb_debug = TRUE; # Set to TRUE to enable extra logging
my $gi_exit_value;
my $gi_current_cmd; # Track what command we are running
my $gs_current_cmd_opt; # For commands that require cmd line options, store it here
my @ga_Cmdline; # Because GetOptions clobbers @ARGV
my $gs_multipath_output;
my $gs_devtable_path;
my $gs_logpath;
my $gs_master_config_loc;
my %gh_master_config_contents;
my @ga_ext_jbodports;
my $gb_xml_option;
my $gb_curnode_option;
my $gb_brief_option;
my $gs_dm_devs;
my $gs_mysqroot;
my $gs_ledstate;
my $gs_nolog;
my $gb_skip_double_mount_ck;
my $gb_local_srp_mount;
my $gb_remote_srp_mount;
my $gs_raidvol_name;

# A few hashes to make it easier to obtain device info
my %gh_uuid_to_mpath :shared;
my %gh_dmdev_to_mpath :shared;
my %gh_uuid_to_dmdev :shared;
my %gh_uuid_to_dev :shared;
my %gh_pvdev_to_name :shared; # For LVM
my %gh_dev_path_state :shared;
my %gh_htl_to_uuid :shared;

##############################################################################
##############################################################################


## Trap warn() calls to redirect to STDERR
## i.e. warnings from GetOptions call in get_cmd()
$SIG{__WARN__} = sub
{
    my $msg = $_[0];
    chomp($msg);

    if (caller() eq "Getopt::Long")
    {
        $msg =~ s/\:/ \-\-/g;

        print STDERR basename($0) . ": $msg\nTry '" . basename($0) .
            " --help' for more information.\n";

        $gi_exit_value = LUNMGR_BAD_CMD_LINE;
    }

    # Normal warn behavior
    else { warn $msg . "  From " . caller(); }
};

## Trap CTRL-C and make sure we close the log, release lock, etc
$SIG{'INT'} = sub
{
    my $device;
    my $cnt = 0;

    die if (!defined($gi_current_cmd)); # Waiting for lunmgr.log lock

    LogMessage(LOG_NOTICE, 0, "Notice: Caught SIGINT, cleaning up before exiting");

    if ($gi_current_cmd == SQCREATE || $gi_current_cmd == LISTJBODS) # Print all the devices found so far
    {
        print "\x08\x08 killed\nCreate interrupted, " . scalar(@ga_alldisks) .
            " devices discovered so far (not written to table):\n\n";

        foreach $device (@ga_alldisks)
        {
            print "  " . $device->uuid .
                (($cnt % 2 == 0) ? "\t" : "\n");
            $cnt++;
        }

        print "\n" if ($cnt % 2 != 0);

        unlink($gs_devtable_path);
        close(DEVTABLE);
    }

    ReadMode('normal') if ($gi_current_cmd == LISTJBODS);
    nunflock($gs_devtable_path);
    nunflock($gs_logpath); # Release the log lock
    print "\n";

    LMExit(LUNMGR_SIGTERM);
};

$SIG{TERM} = sub
{
    my $device;
    my $cnt = 0;

    die if (!defined($gi_current_cmd)); # Waiting for lunmgr.log lock

    LogMessage(LOG_NOTICE, 0, "Notice: Caught SIGTERM, cleaning up before exiting");

    if ($gi_current_cmd == SQCREATE || $gi_current_cmd == LISTJBODS) # Print all the devices found so far
    {
        print "\x08\x08 killed\nCreate interrupted, " . Lunmgr::Device->NumDevs .
            " devices discovered so far (not written to table):\n\n";

        foreach $device (@ga_alldisks)
        {
            print "  " . $device->uuid .
                (($cnt % 2 == 0) ? "\t" : "\n");
            $cnt++;
        }

        print "\n" if ($cnt % 2 != 0);

        unlink($gs_devtable_path);
        close(DEVTABLE);
    }

    ReadMode('normal') if ($gi_current_cmd == LISTJBODS);
    nunflock($gs_devtable_path);
    nunflock($gs_logpath); # Release the log lock
    print "\n";

    LMExit(LUNMGR_SIGTERM);
};

##
## Initialize lunmgr globals, excluding globals that get a value from reading
## the device table or master config file.
##
## Arguments: None
##
## Return Value: None
##
sub InitGlobals()
{
    my $rev = q$Revision: 15188 $;
    my $changedate = q$LastChangedDate: 2012-02-16 16:08:00 -0800 (Thu, 16 Feb 2012) $;

    $rev =~ s/Revision: /r/;
    $rev =~ s/([0-9]+)\s*/$1/;
    $changedate =~ s/LastChangedDate: //;
    $changedate =~ s/\s*[0-9]+:[0-9]+:[0-9]+ .*\s*//;
    $changedate = "(" . $changedate . ")";
    $gs_version = "$rev $changedate";

    my %config = ();

    if (defined($ENV{'MY_SQROOT'})) { chomp($gs_mysqroot = $ENV{'MY_SQROOT'}); }
    else { $gs_mysqroot = ""; }

    if ($gs_mysqroot eq "")
    {
        open(CONFIG, "/opt/hp/nv/lib/lunmgr.conf")
            or die "Couldn't open lunmgr.conf: $!\n";
    }

    else
    {
        open(CONFIG, "$gs_mysqroot/sql/scripts/lunmgr.conf")
            or die "Couldn't open lunmgr.conf: $!\n";
    }

    # Read in the config file
    while (<CONFIG>)
    {
        chomp;

        next if /^\s*\#/; # Comment line
        next unless /=/;

        my ($key, $value) = split(/\s*=\s*/, $_, 2);

        $config{$key} = $value;
    }

    close(CONFIG);

    # Commands we're going to use - may need sudoers entries for these
    $gs_lsscsi_cmd = $config{'lsscsi_loc'};
    $gs_scsiid_cmd = $config{'scsiid_loc'};
    $gs_mount_cmd = $config{'mount_loc'};
    $gs_umount_cmd = $config{'umount_loc'};
    $gs_multipath_cmd = $config{'multipath_loc'};
    $gs_sg_inq_cmd = $config{'sginq_loc'};
    $gs_pvscan_cmd = $config{'pvscan_loc'}; # No sudo necessary
    $gs_qlrescan_cmd = $config{'qlrescan_loc'};
    $gs_smartctl_cmd = $config{'smartctl_loc'};
    $gs_fdisk_cmd = $config{'fdisk_loc'};
    $gs_fuser_cmd = $config{'fuser_loc'};
    $gs_ps_cmd = $config{'ps_loc'};
    $gs_scli_cmd = $config{'scli_loc'};
    $gs_pdsh_cmd = $config{'pdsh_loc'};

    # If we're root, we don't want to (and shouldn't - slower performance) use sudo
    if ($< != 0)
    {
        $gs_scsiid_cmd = "sudo " . $gs_scsiid_cmd if (! -u $config{'scsiid_loc'});
        $gs_mount_cmd = "sudo " . $gs_mount_cmd;
        $gs_umount_cmd = "sudo " . $gs_umount_cmd;
        $gs_multipath_cmd = "sudo " . $gs_multipath_cmd;
        $gs_sg_inq_cmd = "sudo " . $gs_sg_inq_cmd;
        $gs_qlrescan_cmd = "sudo " . $gs_qlrescan_cmd;
        $gs_smartctl_cmd = "sudo " . $gs_smartctl_cmd;
        $gs_fdisk_cmd = "sudo " . $gs_fdisk_cmd;
        $gs_scli_cmd = "sudo " . $gs_scli_cmd;
    }

    # Make sure that each utility specified in the configuration file is executable
    die "Error: " . $config{'lsscsi_loc'} . " is not executable by this user id\n" if (! -x $config{'lsscsi_loc'});
    die "Error: " . $config{'scsiid_loc'} . " is not executable by this user id\n" if (! -x $config{'scsiid_loc'});
    die "Error: " . $config{'mount_loc'} . " is not executable by this user id\n" if (! -x $config{'mount_loc'});
    die "Error: " . $config{'umount_loc'} . " is not executable by this user id\n" if (! -x $config{'umount_loc'});
    die "Error: " . $config{'multipath_loc'} . " is not executable by this user id\n" if (! -x $config{'multipath_loc'});
    die "Error: " . $config{'sginq_loc'} . " is not executable by this user id\n" if (! -x $config{'sginq_loc'});
    die "Error: " . $config{'pvscan_loc'} . " is not executable by this user id\n" if (! -x $config{'pvscan_loc'});
    die "Error: " . $config{'qlrescan_loc'} . " is not executable by this user id\n" if (! -x $config{'qlrescan_loc'});
    die "Error: " . $config{'smartctl_loc'} . " is not executable by this user id\n" if (! -x $config{'smartctl_loc'});
    die "Error: " . $config{'fdisk_loc'} . " is not executable by this user id\n" if (! -x $config{'fdisk_loc'});

    @ga_Cmdline = @ARGV;
    $gi_exit_value = LUNMGR_SUCCESS;

    $gs_multipath_output = "";
    @ga_ext_jbodports = (0, 17, 18, 19, 20, 21, 22, 23); # List of external port numbers in the Fabric OS CLI
#     @ga_ext_jbodports = (0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19, 20, 21, 22, 23); # List of external port numbers in the Fabric OS CLI
    $gs_devtable_path = $config{'devtable_path'} . "/lunmgr.devtable";
    %gh_master_config_contents = ();
    $gb_xml_option = FALSE;
    $gb_skip_double_mount_ck = FALSE;
    %gh_uuid_to_mpath = ();
    %gh_dmdev_to_mpath = ();
    $gs_dm_devs = "";
    $gs_nolog = 0;
    $gs_fcpass = "";

    return (TRUE) if (uc($config{'disabled'}) eq "TRUE");
    return (FALSE);
}

##
## When doing a table create, use the master configuration file to build out
## the device table.
##
## Arguments: None
##
## Return Value: None
##
sub ParseConfig()
{
    my (@jbod_luns, $jbod_wwn, @jbods, $lunnum, $lunmt, $lunname, $configTwig, $lunuuid);
    my ($switch, @switches, @racks);

    LogMessage(LOG_DEBUG, 0, "***ParseConfig - Enter") if ($gb_trace_function_calls);

    # Master config file not found, issue a warning and build the device table
    # with "default" entries (e.g. no volume name or valid mount point).
    # Mostly only useful in testing.
    if (! -e $gs_master_config_loc)
    {
        LogMessage(LOG_WARNING, 0, "Master config file " .
            "($gs_master_config_loc) doesn't exist, using defaults");
    }

    else
    {
        $configTwig = new XML::Twig;
        $configTwig->parsefile($gs_master_config_loc);
        my $xmlRoot = $configTwig->root;
        @racks = $xmlRoot->children;

        # Loop through each switch in the master config (<switch>)
        for my $rack (@racks)
        {
            @switches = $rack->children;

            for my $switch (@switches)
            {
                @jbods = $switch->children;

                # Loop through each JBOD in the master config (<disk_enclosure>)
                for my $jbod (@jbods)
                {
                    @jbod_luns = $jbod->children;
                    $jbod_wwn = $jbod->att('wwpn');

                    for my $lun (@jbod_luns)
                    {
                        $lunnum = $lun->att('id');
                        $lunmt = $lun->first_child_text('mount_pt');
                        $lunname = $lun->first_child_text('name');
                        $lunuuid = $lun->first_child_text('device');

                        if ((!defined($gh_master_config_contents{$lunuuid})) ||
                            (defined($gh_master_config_contents{$lunuuid}) && $gh_master_config_contents{$lunuuid}{'mount'} eq "NONE"))
                        {
                            $gh_master_config_contents{$lunuuid}{'mount'} = $lunmt;
                            $gh_master_config_contents{$lunuuid}{'name'} = $lunname;
                        }
                    }
                }
            }
        }
    }

    LogMessage(LOG_DEBUG, 0, "***ParseConfig - Leave") if ($gb_trace_function_calls);
}

##
## Parse command line options and also open the FH for the devtable and/or
## master config as needed.
##
## Arguments: None
##
## Return Value: Integer constant representing the command.
##
sub ParseCmdline()
{
    my ($volume, @fields, $retval, $cmdAllowed, $count, $cmdRet);
    my ($errmsg, $dtperms, $mcperms, $dtlock, $mclock, $openres);
    my $fh = "devtable";    # By default open devtable in OpenFile call

    LogMessage(LOG_DEBUG, 0, "Command line: \"@ga_Cmdline\"");

    # Incorrect number of options given - print usage info
    # Need to check this before GetOptions clobbers @ARGV
    if ($#ARGV < 0)
    {
        $errmsg = basename($0) . ": Command line option is required\n" .
            "Try '" . basename($0) . " --help' for more information\n";

        print STDERR $errmsg;

        LMExit(LUNMGR_BAD_CMD_LINE);
    }

    # Note the use of umask(0) when creating the device table, which allows
    # for the 0666 perms to be retained (i.e. udev may to need to be
    # able to write to the device table
    my $getOptRes = GetOptions
    (
        'add|a|sqadd' =>
            sub { $cmdRet = SQADD; },

        'brief|b' =>
            sub { $gb_brief_option = TRUE; },

        'create|c:s' =>
            sub { $cmdRet = SQCREATE;
                  $gs_master_config_loc = $_[1] if $_[1]; },

        'currentnode' =>
            sub { $gb_curnode_option = TRUE; },

        'fcpass=s' =>
            sub { $gs_fcpass = $_[1]; },

        'help|h' =>
            sub { $cmdRet = HELP },

        'local' =>
            sub { $gb_local_srp_mount = TRUE; },

        'mkfs=s' =>
            sub
            {
                $cmdRet = MKFS;
                $gs_current_cmd_opt = $_[1];
            },

        'mount|m|sqmount=s' =>
            sub { $cmdRet = MOUNT;
                  $gs_current_cmd_opt = "";

                  foreach my $vol (split(/,\s*/, $_[1]))
                  {
                      $gs_current_cmd_opt .= (($gs_current_cmd_opt ne "") ? "," : "");
                      $gs_current_cmd_opt .= $vol;
                  }
            },

        'print|p:s' =>
            sub { $cmdRet = PRINT;
                  if ($_[1])
                  {
                      $gs_current_cmd_opt = "";

                      foreach my $vol (split(/,\s*/, $_[1]))
                      {
                          $vol = substr($vol, 1) if ($vol =~ /^\$/); # Trim leading '$'
                          $gs_current_cmd_opt .= (($gs_current_cmd_opt ne "") ? "," : "");
                          $gs_current_cmd_opt .= $vol;
                      }
                  }
            },

        'rack=s' =>
            sub { $gs_current_cmd_opt = $_[1]; },

        'raidvol=s' =>
            sub { $gs_raidvol_name = $_[1]; },

        'remove|r|sqremove=s' =>
            sub { $cmdRet = REMOVE;
                  $gs_current_cmd_opt = $_[1]; },

        'remote' =>
            sub { $gb_remote_srp_mount = TRUE; },

        'setled|sl=s' =>
            sub { $cmdRet = LEDCMD;
                  $gs_current_cmd_opt = $_[1]; },

        'showtopology|s' =>
            sub { $cmdRet = LISTJBODS },

        'nodoublemount' =>
            sub { $gb_skip_double_mount_ck = TRUE; },

        'status=s' =>
            sub { $cmdRet = STATUS;
                  if ($_[1])
                  {
                      $gs_current_cmd_opt = "";

                      foreach my $vol (split(/,\s*/, $_[1]))
                      {
                          $vol = substr($vol, 1) if ($vol =~ /^\$/); # Trim leading '$'
                          $gs_current_cmd_opt .= (($gs_current_cmd_opt ne "") ? "," : "");
                          $gs_current_cmd_opt .= $vol;
                      }
                  }
            },

        'unmount|u|squnmount=s' =>
            sub { $cmdRet = UNMOUNT;
                  $gs_current_cmd_opt = $_[1]; },

        'unmountall|squnmountall' =>
            sub { $cmdRet = UNMOUNTALL },

        'version|v' =>
            sub { $cmdRet = GETVERSION; },

        'xml|x' =>
            sub { $gb_xml_option = TRUE; }
    );

    # Unrecognized command line option - warn trapped, msg printed already
    LMExit(LUNMGR_BAD_CMD_LINE) if (!$getOptRes);

    if ($gb_xml_option)
    {
        if (!defined($cmdRet) || ($cmdRet != LISTJBODS && $cmdRet != PRINT))
        {
            print STDERR basename($0) . ": Option xml requires an additional option\nTry '" . basename($0) .
                " --help' for more information.\n";
            LMExit(LUNMGR_BAD_CMD_LINE);
        }
    }

    $gs_ledstate = $ARGV[0] if ($cmdRet == LEDCMD);

    # Need to specify turning on or off the LED
    if (!defined($gs_ledstate) && $cmdRet == LEDCMD)
    {
        print STDERR basename($0) . ": Option sl requires an additional option\nTrye '" . basename($0) .
            " --help' for more information.\n";
        LMExit(LUNMGR_BAD_CMD_LINE);
    }

    # Only allow "brief" option when doing a print command
    $gb_brief_option = FALSE if ($cmdRet != PRINT && $gb_brief_option);

    return ($cmdRet);
}

##
## Display help menu to STDOUT.
##
## Arguments: None
##
## Return Value: None
##
sub PrintUsage()
{
    my $lmname = basename($0);

    print <<END

Usage: $lmname [option]...

 Maintain relationship between SeaQuest logical volume and physical devices.

 Where main options are:

   --add              Add new LUNs to the device table.  This command can be
                      used to replace removed disks, or to detect new disks
                      that have been added to the system.
   --create <file>    Create master device table based on global system
                      configuration file.  Optional argument <file> can be used
                      to specify the configuration file location, otherwise the
                      default /hptc_cluster/lunmgr.config is used.
   --help             Display this usage information and exit.
   --mount <vollist>  Mount either a single LUN or a comma-separated list of
                      LUNs associated with the names in the <vollist>.  Single
                      volumes in the list must be of the form /mount_point/NAME.
                      Priority is to mount a DM-Multipath device, with the
                      fallback option of mounting a standard SCSI (/dev/sdX)
                      device.
   --print <vollist>  Display entire device table contents to the terminal and
                      exit.  Optional volume list <vollist> can be used to only
                      display information associated with a list of \$VOLUME(s),
                      in a comma-separated list with optional leading dollar
                      signs.
   --remove <path>    In the event of a disk failure, this call will cause the
                      the LUNs associated with a given device removed from the
                      system.  <path> argumement will be in the form of
                      /database[m]/\$VOLUME.
   --sl NAME [on|off] Turn the disk UID LED on or off for a given volume name.
                      The name will be in the form of /mount_point/NAME.
   --showtopology     Print a list of attached JBODs, as seen from the hosts's
                      perspective.
   --unmount NAME     Unmount physical disk associated with logical volume path
                      NAME, e.g. in the form of /database[m]/\$VOLUME.
   --unmountall       Unmount all external disks in use on the executing node.
                      When called from a SeaQuest userid (e.g. monitor process),
                      unmount all disks specified in the user's sqconfig file.
                      If run as root, unmount all disks $lmname knows about.
   --version          Output version information and exit.

 Additional options, which may be used in conjunction with certain $lmname
 commands:

   --brief            Display less verbose volume information.  Valid base
                      command(s): --print.  When used with the --print <H:T:L>
                      format, only the volume name will be printed.
   --currentnode      Restrict the current $lmname command to the executing node
                      only.  Valid base command(s): --print.  When used in
                      conjunction with --print, $lmname will only display
                      information for those devices mounted on the current node.
   --rack             Assign a rack number (where 1 is the first rack) for the
                      current command.  Valid base command: --showtopology.
   --xml              Output results in XML format.  Valid base command(s):
                      --showtopology, --print.

END
}

##
## Discover attached fibre channel disks, by using information in
## /dev/disk/by-path.
##
## Arguments: None
##
## Return Value: None
##
sub FindFCDisks()
{
    my ($alldisks, $line, @fields, $dev, $uuid, $lun, @lun_fields, @dev_fields);
    my ($target_wwn, $device, $lsscsiout, $scsiidout, @target_wwns, $devtype);
    my $fc_count = 0;
    my @symbol = qw(| / - \\);
    my $alldisks_cmd = "by-path";

    LogMessage(LOG_DEBUG, 0, "***FindFCDisks - Enter") if ($gb_trace_function_calls);

    my $fccards = `/sbin/lspci | grep QLogic 2>&1`;

    # No FC card listed in 'lspci' output
    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
    {
        LogMessage(LOG_NOTICE, 0, "It doesn't appear that this node has Fibre Channel storage attached");
    }

    # This system appears to have an FC card
    else
    {
        # List all the FC disks - they will only show the transport here if the
        # qla2xxx driver is using the scsi_transport_fc module (and not intermodule)
        $alldisks = `ls -l /dev/disk/by-path | grep "\\-fc\\-" | 
            egrep -v "part[0-9]+ \\->"`;

        if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
        {
            chomp($alldisks = `$gs_lsscsi_cmd`);
            $alldisks_cmd = "lsscsi";
        }

        LogMessage(LOG_DEBUG, 0,
            sprintf("Using %s to discover Fibre Channel devices",
                ($alldisks_cmd eq "lsscsi") ? "lsscsi" : "/dev/disk/by-path"));

        print "Discovering Fibre Channel devices...  ";

        foreach $line (split("\n", $alldisks))
        {
            @fields = split(/\s+/, $line);
            $dev = $fields[-1];

            @dev_fields = split("/", $dev);
            $dev = $dev_fields[-1];

            if ($alldisks_cmd eq "by-path")
            {
                $uuid = $fields[-3];
                $uuid =~ m/-fc-0x([0-9a-f]{16}):0x([0-9a-f]{4})/; # Get port WWN and LUN
                $target_wwn = $1;
                $lun = hex($2);
            }

            else
            {
                chomp($uuid = `$gs_sg_inq_cmd -p 0x83 /dev/$dev`);
                $uuid =~ m/\[0x([0-9a-f]{16})\]/;
                $target_wwn = $1;

                $lun = $fields[0]; # Get the [H:B:T:L]
                @lun_fields = split(/:/, $lun);
                $lun = $lun_fields[-1];
                chop($lun); # Need to remove the trailing "]" character
            }

            # Create a new instance of the Device class
            $device = Lunmgr::Device->new
            (
                "WWN" => $target_wwn,
                "LUN" => $lun,
                "DEV" => $dev
            );

            # Match only sdX device at end of the line, e.g. if we are searching
            # for sdb, we don't want to also match sdba, sdbb, etc.
            $lsscsiout = `$gs_lsscsi_cmd | egrep "$dev *\\\$"`; # Sometimes there's a space after the sdX name
            @fields = split(" ", $lsscsiout);

            chomp($scsiidout = `$gs_scsiid_cmd -gus /block/$dev`);
            chomp($devtype = `cat /sys/block/$dev/device/model`);

            $devtype =~ s/\s+$//; # Remove any trailing spaces
            $devtype =~ s/ /_/g;  # Replace internal spaces with underscores

            $device->type($devtype); # Get the device type (MSA, etc)
            $device->uuid($scsiidout);
            $device->mpath(GetMpathName($scsiidout)) if ($gi_current_cmd != SQCREATE); # Optimization, don't need mpath when creating devtable
            $device->name("FREE"); # Default, will be replaced by value from master config if we're doing a "create"
            $device->mount_point("NONE"); # Default, can be replaced by value from master config

            # Rewrite the name and mount point based on the master config file,
            # if it was specified.
            if ($gi_current_cmd == SQCREATE)
            {
                # Pick up the mount point for this device from the master config
                if (defined($gh_master_config_contents{$device->uuid}{'mount'})
                    && $gh_master_config_contents{$device->uuid}{'mount'} ne "")
                {
                    LogMessage(LOG_DEBUG, 0, "Modifying mount point to " . $gh_master_config_contents{$device->uuid}{'mount'} . " for " . $device->uuid .
                        " from master configuration");
                    $device->mount_point($gh_master_config_contents{$device->uuid}{'mount'});
                }

                # Pick up the name for this device from the master config
                if (defined($gh_master_config_contents{$device->uuid}{'name'})
                    && $gh_master_config_contents{$device->uuid}{'name'} ne "")
                {
                    LogMessage(LOG_DEBUG, 0, "Modifying name to " . $gh_master_config_contents{$device->uuid}{'name'} . " for " . $device->uuid .
                        " from master configuration");
                    $device->name($gh_master_config_contents{$device->uuid}{'name'});
                }

                # Try to stop us from targeting /hptc_cluster or /dumps, which would be
                # a bad idea.
                if (defined($gh_uuid_to_dmdev{$device->uuid}) &&
                    defined($gh_pvdev_to_name{$gh_uuid_to_dmdev{$device->uuid}}))
                {
                    LogMessage(LOG_NOTICE, 0, "Modifying " . $device->uuid .
                        ": managed by LVM");
                    $device->name($gh_pvdev_to_name{$gh_uuid_to_dmdev{$device->uuid}});
                }
            }

            elsif ($gi_current_cmd == LISTJBODS)
            {
                # Bright stuff
                if ($lun == 60)
                {
                    $device->name("home");
                    $device->mount_point("/");
                }

                elsif ($lun == 61)
                {
                    $device->name("shared");
                    $device->mount_point("/cm");
                }
            }

            push(@ga_alldisks, $device);
            $fc_count++;
            print "\x08";
            print $symbol[$fc_count % 4];

            LogMessage(LOG_DEBUG, 0, sprintf("New device: %s - %s", $device->uuid, $dev));
        }

        print "\x08\x08 $fc_count found\n";
        LogMessage(LOG_INFO, 0, "Fibre Channel devices: " . $fc_count);
    }

    LogMessage(LOG_DEBUG, 0, "***FindFCDisks - Leave") if ($gb_trace_function_calls);
}

##
## Discover attached SAS disks, by using information in
## /dev/disk/by-path (or lsscsi if those devices don't exist).
##
## Arguments: None
##
## Return Value: None
##
sub FindSASDisks()
{
    my ($all_sas_disks, @fields, $dev, @dev_fields, $scsiidout, $lun, $target_wwn);
    my ($line, @lun_fields, $device, $sysfspath, $lsscsiout, $jbod, $sgdev, $type);
    my $sasdisks_cmd = "by-path";
    my $sas_count = 0;
    my @symbol = qw(| / - \\);

    LogMessage(LOG_DEBUG, 0, "***FindSASDisks - Enter") if ($gb_trace_function_calls);

    my $sascards = `lspci | grep SAS1068E 2>&1`;

    # No SAS card listed in 'lspci' output
    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
    {
        LogMessage(LOG_NOTICE, 0, "It doesn't appear that this node has SAS storage attached");
    }

    else
    {
        # List all the FC disks - they will only show the transport here if the
        # SAS driver is using the scsi_transport_sas module (and not intermodule)
        $all_sas_disks = `ls -l /dev/disk/by-path | grep "\\-sas\\-" | 
            egrep -v "part[0-9]+ \\->"`;

        if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
        {
            $all_sas_disks = `$gs_lsscsi_cmd | grep disk`;
            chomp($all_sas_disks);
            $sasdisks_cmd = "lsscsi";
        }

        LogMessage(LOG_DEBUG, 0,
            sprintf("Using %s to discover SAS devices",
                ($sasdisks_cmd eq "lsscsi") ? "lsscsi" : "/dev/disk/by-path"));

        print "Discovering SAS devices...  ";

        $lsscsiout = `$gs_lsscsi_cmd -g | grep enclos`; # Get the JBODs

        foreach $line (split("\n", $all_sas_disks))
        {
            @fields = split(/\s+/, $line);
            $dev = $fields[-1];
            @dev_fields = split("/", $dev);
            $dev = $dev_fields[-1];

            $sysfspath = "/sys/block/$dev/device";
            chdir($sysfspath); # Follow the symlink
            chomp($scsiidout = `$gs_scsiid_cmd -gus /block/$dev`);
            chomp($target_wwn = `cat ../../sas_device*/enclosure_identifier 2> /dev/null`);
            chomp($lun = `cat ../../sas_device*/bay_identifier 2> /dev/null`);

            $target_wwn =~ s/^0x//; # Remove leading "0x"

            # Create a new instance of the Device class
            $device = Lunmgr::Device->new
            (
                "WWN"  => $target_wwn,
                "LUN"  => $lun,
                "DEV"  => $dev,
                "UUID" => $scsiidout
            );

            $device->name("NONAME"); # Default, can be replaced by value from master config if we're doing a "create"
            $device->mount_point("NOMOUNT"); # Default, can be replaced by value from master config

            # Try to find the JBOD that matches the $target_wwn, to get the type
            foreach $jbod (split("\n", $lsscsiout))
            {
                @fields = split(/\s+/, $jbod);
                $sgdev = $fields[-1];
                @dev_fields = split("/", $sgdev);
                $sgdev = $dev_fields[-1];
                $sysfspath = "/sys/class/scsi_generic/$sgdev/device";
                chdir($sysfspath);
                chomp($type = `cat model`);
                chomp($target_wwn = `cat ../../sas_device*/enclosure_identifier 2> /dev/null`);
                $target_wwn =~ s/^0x//; # Remove leading "0x"
                $type =~ s/\s+$//;

                if ($device->target_wwn eq $target_wwn)
                {
                    $device->type($type);
                    last;
                }
            }

            # Rewrite the name and mount point based on the master config file,
            # if it was specified.
            if ($gi_current_cmd == SQCREATE)
            {
                # Pick up the mount point for this device from the master config
                if (defined($gh_master_config_contents{$target_wwn}{$lun}{'mount'})
                    && $gh_master_config_contents{$target_wwn}{$lun}{'mount'} ne "")
                {
                    LogMessage(LOG_DEBUG, 0, "Modifying mount point for " . $device->uuid .
                        " from master configuration");
                    $device->mount_point($gh_master_config_contents{$target_wwn}{$lun}{'mount'});
                }

                # Pick up the name for this device from the master config
                if (defined($gh_master_config_contents{$target_wwn}{$lun}{'name'})
                    && $gh_master_config_contents{$target_wwn}{$lun}{'name'} ne "")
                {
                    LogMessage(LOG_DEBUG, 0, "Modifying name for " . $device->uuid .
                        " from master configuration");
                    $device->name($gh_master_config_contents{$target_wwn}{$lun}{'name'});
                }
            }

            push(@ga_alldisks, $device);
            $sas_count++;
            print "\x08";
            print $symbol[$sas_count % 4];

            LogMessage(LOG_DEBUG, 0, sprintf("New device: %s - %s", $device->uuid(), $dev));
        }

        print "\x08\x08 $sas_count found\n";
        LogMessage(LOG_INFO, 0, "SAS devices: " . $sas_count);
    }

    LogMessage(LOG_DEBUG, 0, "***FindSASDisks - Leave") if ($gb_trace_function_calls);
}

##
## Print devices from memory (@ga_alldisks) to STDOUT.  All the disks in the
## devtable may be printed, or a subset, as specified on the command line.
##
## Arguments: None
##
## Return Value: None
##
sub PrintAllDevices()
{
    my ($device, $dmdev, $slaves, $cmdstr, $cmdres, $match, $name_no_dsign);
    my $prevwwn = "";
    my $count = 0; # Keep count
    my @devs_to_print = split(",", $gs_current_cmd_opt);

    LogMessage(LOG_DEBUG, 0, "***PrintAllDevices - Enter") if ($gb_trace_function_calls);

    if ($gb_xml_option)
    {
        print "<?xml version=\"1.0\" ?>\n<lunmgr_conf>\n";
    }

    else
    {
        print "\n***" . scalar(@ga_alldisks) . " LUNs in the device table***\n"
            unless ($gb_brief_option);
    }

    for $device (sort{$a->target_wwn cmp $b->target_wwn} @ga_alldisks)
    {
        $slaves = ""; # Reset slave devices each time through
        $name_no_dsign = $device->name;
        $name_no_dsign = substr($device->name, 1) if ($device->name =~ /^\$/);

        # Print only current mounted device information
        if ($gb_curnode_option)
        {
            $cmdstr = "grep '" . $device->mount_point . "/" . $device->name . " ' /proc/mounts";
            $cmdres = `$cmdstr > /dev/null 2>&1`; # Don't care about the output

            next if (WIFEXITED($?) && (WEXITSTATUS($?) != 0));
        }

        # Print info only for a specific volume
        if (defined($gs_current_cmd_opt))
        {
            $match = FALSE;

            for (@devs_to_print)
            {
                # Looking for information about H:T:L
                if ($_ =~ /[0-9]+:[0-9]+:[0-9]+/)
                {
                    $match = TRUE if ($gh_htl_to_uuid{"[$_]"} eq $device->uuid());
                }

                $match = TRUE if (uc($_) eq uc($name_no_dsign));

                if ($device->mount_point() ne "/")
                {
                    $match = TRUE
                        if ($_ eq $device->mount_point() . "/" . $device->name());
                }

                # Handle the case of an entry in the device table that mounts directly underneath
                # /, such as /home
                else
                {
                    $match = TRUE
                        if ($_ eq $device->mount_point() . $device->name());
                }
            }

            next if (!$match); # Current $device not matched in @devs_to_print
        }

        # Brief output
        if ($gb_brief_option)
        {
            print $device->mount_point() . "/" . $device->name() . "\n";
            $count++;
            next;
        }

        # $device->dev() may not be defined if the PV's aren't visible on this node
        if (defined($device->dev))
        {
            for my $dev (split(/\s+/, $device->dev))
            {
                $device->name("PV: " . $gh_pvdev_to_name{$dev}) if (defined($gh_pvdev_to_name{$dev}));
            }
        }

        # Display the LVM info, if applicable
        if (defined($gh_uuid_to_dmdev{$device->uuid()}) &&
            defined($gh_pvdev_to_name{$gh_uuid_to_dmdev{$device->uuid()}}))
        {
            $device->name("PV: " . $gh_pvdev_to_name{$gh_uuid_to_dmdev{$device->uuid()}});
        }

        if (!$gb_xml_option)
        {
            print $device->ToString();
            $count++;

            # Not visible on this node!
            if (!defined($device->dev()) && !defined($device->mpath()))
            {
                LogMessage(LOG_INFO, 0, sprintf("Device %s is not visible on this node", $device->uuid()));
                print " - DEVICE NOT VISIBLE\n";
                next;
            }

            # Skip to next device if there is no mpath alias (it will have no slaves)
            # But it still should have a /dev/sd* device
            if (!defined($device->mpath()))
            {
                LogMessage(LOG_INFO, 0, sprintf("Device %s not managed by DM-Multipath", $device->uuid()));
                print "\n  Device(s): " . $gh_uuid_to_dev{$device->uuid()} . "\n";
                next;
            }

            print ", with paths [";
            $dmdev = $gh_uuid_to_dmdev{$device->uuid()};
            open(LS, "ls /sys/block/$dmdev/slaves |");

            my @sds;

            while (<LS>)
            {
                chomp($_);
                push(@sds, $_);
                $slaves .= "$_,";
            }

            chop($slaves);
            print "$slaves]\n  Path status: ";
            print $sds[0] . "->" . $gh_dev_path_state{$sds[0]} . ", ";
            print $sds[1] . "->" . $gh_dev_path_state{$sds[1]} . "\n";
        }

        else
        {
            $count++;

            if ($prevwwn ne $device->target_wwn())
            {
                print "  </disk_enclosure>\n" if ($prevwwn ne "");
                print "  <disk_enclosure wwpn=\"" . $device->target_wwn() . "\">\n";
                $prevwwn = $device->target_wwn();
            }

            print $device->ToXMLString(\%gh_dev_path_state);
        }
    }

    if ($gb_xml_option)
    {
        print "  </disk_enclosure>\n</lunmgr_conf>\n";
    }

    else { print "\n" unless ($gb_brief_option); }

    print "No devices currently in use on this node.\n" if ($gb_curnode_option && $count == 0);

    LogMessage(LOG_DEBUG, 0, "***PrintAllDevices - Leave") if ($gb_trace_function_calls);

    # Did we print each device on the command line?
    if ($count < scalar(@devs_to_print))
    {
        print STDERR "  WARNING: One or more devices specified not found in device table!\n\n";
        return (LUNMGR_DEVICE_NOT_FOUND);
    }

    else { return (LUNMGR_SUCCESS); }
}

##
## Read in the contents of lunmgr.devtable, and fill the global device array
## with all the entries.
##
## Arguments: One - required
##              @_[0] - Mode to open the device table in (e.g. O_RDONLY)
##
## Return Value: None
##
sub OpenDevtable($)
{
    my $dtperms = shift(@_);
    my $dtdir = dirname($gs_devtable_path);

    LogMessage(LOG_DEBUG, 0, "***OpenDevtable - Enter") if ($gb_trace_function_calls);

    if ($gi_current_cmd == SQCREATE && -e $gs_devtable_path)
    {
        LogMessage(LOG_INFO, 0, "Appending to existing device table")
    }

    # May need to create the /hptc_cluster/lunmgr directory
    mkdir($dtdir) if ($gi_current_cmd == SQCREATE && ! -e $dtdir);

    sysopen(DEVTABLE, $gs_devtable_path, $dtperms)
        or die("Error opening/creating device table: $!");

    if ($dtperms != O_RDONLY)
    {
        while (TRUE) { last if (nflock($gs_devtable_path)); }
    }

    LogMessage(LOG_DEBUG, 0, "***OpenDevtable - Leave") if ($gb_trace_function_calls);
}

##
## Read in the contents of lunmgr.devtable, and fill the global device array
## with all the entries.
##
## Arguments: None
##
## Return Value: None
##
sub ReadDevtable()
{
    my ($uuid, $type, $enclosure, $mntpt, $mpath, $lun, $targetstr, $device, $name, $dev);
    my ($twig_thread, $lvm_thread);

    my $tc = TimeCheck->new(STATEMENT => "ReadDevtable");
    LogMessage(LOG_DEBUG, 0, "***ReadDevtable - Enter") if ($gb_trace_function_calls);

    ($twig_thread) = threads->create(\&BuildMSATwigs) unless ($gb_brief_option);

    $lvm_thread = threads->create(\&DiscoverLVM);

    BuildDeviceHashes();

    $lvm_thread->join();
    @ga_allMSAs = $twig_thread->join() unless ($gb_brief_option);

    # Slirp in the device table contents
    my $lineno = 0;
    while (<DEVTABLE>)
    {
        $lineno++;
        ($uuid, $type, $enclosure, $mntpt, $name) = split(/\s+/, $_);
        ($targetstr, $lun) = split(":", $enclosure);

        if (!defined ($uuid) || length($uuid) == 0)
        {
            LogMessage(LOG_WARNING, 0, "Empty uuid at line $lineno in lunmgr.devtable, skipped");
            next;
        }

        # Create a new instance of the Device class
        $device = Lunmgr::Device->new
        (
            "UUID"   => $uuid,
            "TYPE"   => $type,
            "LUN"    => $lun,
            "MNTPT"  => $mntpt,
            "NAME"   => $name
        );

        if ($targetstr =~ m/[0-9a-f]{16}/)
        {
            $device->target_wwn($targetstr);
        }
        elsif ($targetstr =~ m/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\./)
        {
            $device->target_ip($targetstr);
        }
        else
        {
            LogMessage(LOG_WARNING, 0, "Invalid enclosure \"$enclosure\" at line $lineno in lunmgr.devtable, skipped");
            next;
        }

        # Add mpath to the device here
        $mpath = $gh_uuid_to_mpath{$uuid};
        $dev = $gh_uuid_to_dev{$uuid};

        $device->mpath($mpath);
        $device->dev($dev);

        my $dev_uuid_nozero = $device->uuid_no_zero();

        # If we're trying to be brief (on --print command), we don't care about MSA info
        unless ($gi_current_cmd == PRINT && $gb_brief_option)
        {
            # Loop through MSAs (to get IP address)
            MSAS: for my $msa (@ga_allMSAs)
            {
                my $tc_msa = TimeCheck->new();
                my $ports_ref = $msa->GetHostPortsStatus();
                $tc_msa->show($msa->ip() . ":GetHostPortsStatus");

                for my $port (@{$ports_ref})
                {
                    my $tc_port = TimeCheck->new();
                    # Check if the current MSA is the one hosting this $device
                    if (uc(${$port}{'port_wwn'}) eq uc($device->target_wwn))
                    {
                        my $vdref = $msa->GetVdisks(1);
                        $tc_port->show($msa->ip() . ":GetVdisks(1)");

                        for my $vdisk (sort({$a->Name() cmp $b->Name()} @{$vdref}))
                        {
                            my $tc_vdisk = TimeCheck->new();
                            my $volref = $vdisk->GetVolumes();
                            $tc_vdisk->show($msa->ip() . "_" . $vdisk->Name() . ":GetVolumes()");

                            for my $vol (@{$volref})
                            {
                                my $vol_uuid_nozero = $vol->IDNoZero();

                                if ($dev_uuid_nozero =~ /$vol_uuid_nozero/)
                                {
                                    $device->target_ip($msa->ip()); # Set the IP
                                    last MSAS;
                                }
                            }
                        }
                    }
                }
            }
        }

        push(@ga_alldisks, $device);
    }

    LogMessage(LOG_DEBUG, 0, "Number of unique LUNs read from table: " . Lunmgr::Device->NumDevs());
    LogMessage(LOG_DEBUG, 0, "***ReadDevtable - Leave") if ($gb_trace_function_calls);

    $tc->show();
}

##
## Write devices from memory out to the device table.  Note that multiple
## device entries are not written (i.e. when a disk has multiple paths).
##
## Arguments: One - required
##              @_[0] (R) - Array containing contents of master config XML
##                          file, stored as XML::Twig elements.
##
## Return Value: None
##
sub WriteAllDevices()
{
    my %devs_printed;

    LogMessage(LOG_DEBUG, 0, "***WriteAllDevices - Enter") if ($gb_trace_function_calls);

    for my $device (@ga_alldisks)
    {
        my $target = $device->target_wwn();
        my $lun = $device->lun();

        # Make sure we don't print out duplicate paths
        # Reduces clutter in the devtable
        if (!(exists($devs_printed{$device->uuid()})) ||
            ($devs_printed{$device->uuid()} ne "complete"))
        {
            print DEVTABLE $device->ToTableString();
            $devs_printed{$device->uuid()} = "complete";
        }

        else
        {
            LogMessage(LOG_DEBUG, 0, "Duplicate device [" .
                $device->uuid() . "]")
        }
    }

    LogMessage(LOG_INFO, 0, "Number of unique devices written to table: " . Lunmgr::Device->NumDevsWritten);
    LogMessage(LOG_DEBUG, 0, "***WriteAllDevices - Leave") if ($gb_trace_function_calls);
}

##
## Look for LVM-managed devices on the system.  This helps identify, in
## particular, /hptc_cluster and /dumps when doing a --print or --create.
##
## Arguments: None
##
## Return Value: None
##
sub DiscoverLVM()
{
    my (@fields, $pvdev, $pvs, $lvname, $slaves, $slavecmd);
    my $lvcount = 0;

    my $tc = TimeCheck->new(STATEMENT => "DiscoverLVM");
    LogMessage(LOG_DEBUG, 0, "***DiscoverLVM - Enter") if ($gb_trace_function_calls);

#     if ($gi_current_cmd != PRINT)
    if (FALSE)
    {
        $pvs = `$gs_pvscan_cmd 2>/dev/null | grep PV`;

        if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
        {
            LogMessage(LOG_INFO, 0, "Info - no physical volumes found, or no LVM block devices configured");
        }

        else
        {
            chomp($pvs);

            foreach my $pv (split("\n", $pvs))
            {
                @fields = split(/\s+/, $pv);
                $pvdev = $fields[2];
                $lvname = $fields[4];
                @fields = split("/", $pvdev);
                $pvdev = $fields[-1];
                chop($pvdev) if ($pvdev =~ /sd[a-z]+[0-9]+/); # Remove the partition number, if PV is something like "sda1"

                # Sometimes the dm-* devices are just aliases for another dm-* device
                # We check here to make sure we select the dm-* device that showed
                # up when we used the multipath command.
                # For example: 'pvscan' shows a PV of /dev/dm-16, but if we look at
                # /sys/block/dm-16/slaves, we don't see sd* devices, we see another
                # dm-* device, e.g. dm-206.  So we change $pvdev to be dm-206
                # (from dm-16) and then look at /sys/block/dm-16/slaves/dm-206/slaves
                # and recurse down until we get SCSI block devices
                if ($pvdev =~ /dm-[0-9]+/)
                {
                    $slavecmd = "ls -d /sys/block/$pvdev";
                    do
                    {
                        $slavecmd .= "/slaves/*";
                        $slaves = `$slavecmd | xargs basename`;
                        chomp($slaves);
                        $pvdev = $slaves if ($slaves =~ /dm-[0-9]+/);
                    } while ($slaves !~ /sd[a-z]+/);
                }

                $gh_pvdev_to_name{$pvdev} = $lvname;
                $lvcount++;
            }
        }
    }

    LogMessage(LOG_DEBUG, 0, "***DiscoverLVM - Leave") if ($gb_trace_function_calls);
    $tc->show();
}

##
## Build up an array of MSA::Enclosure objects, using what's found in the
## hosts file.  By default, we try to use the "cache" XML files that are
## stored under /home/lunmgr/msa_xml in order to speed up the operation.
##
## Arguments: None
##
## Return Value: Array of MSA::Enclosure objects
##
sub BuildMSATwigs()
{
    my ($twig_root, $ip, $name);
    my $count = 0;
    my @ret;

    my $tc = TimeCheck->new(STATEMENT => "BuildMSATwigs");
    LogMessage(LOG_DEBUG, 0, "***BuildMSATwigs - Enter") if ($gb_trace_function_calls);

    if ($gi_current_cmd == LEDCMD || $gi_current_cmd == PRINT)
    {
        my $allmsas = `egrep -o msa[0-9]+ /etc/hosts | uniq 2>/dev/null`;
        my @msalist = split("\n", $allmsas);

        # Search for the MSA entries in /etc/hosts
        for my $msaname (@msalist)
        {
            my $msa;

            my $msafile = "/home/lunmgr/msa_xml/$msaname.xml";
            my $msafile2 = "/home/lunmgr/msa_xml/${msaname}_init.xml";

            if (-e $msafile)
            {
                $msa = MSA::Enclosure->new(XMLFILE => $msafile); # Create the instance
            }
            elsif (-e $msafile2)
            {
                $msa = MSA::Enclosure->new(XMLFILE => $msafile2); # Create the instance
            }

            else { 
               chomp (my $msaip = `egrep -m 1 -w $msaname /etc/hosts 2>/dev/null | cut -d' ' -f1`);
               $msa = MSA::Enclosure->new(IP => $msaip); 
            }

            push(@ret, $msa);
            $count++;
        }

        if ($count == 0) { LogMessage(LOG_NOTICE, 0, "Warning: No MSA IPs discovered in /etc/hosts"); }
        else { LogMessage(LOG_INFO, 0, "$count MSAs discovered in /etc/hosts"); }
    }

    LogMessage(LOG_DEBUG, 0, "***BuildMSATwigs - Leave") if ($gb_trace_function_calls);

    $tc->show();
    return (@ret);
}

##
## Build up the three global device hashes containing multipath information:
## %gh_uuid_to_mpath, %gh_dmdev_to_mpath, and %gh_uuid_to_dmdev.  Run in a
## separate thread for performance benefit.
##
## Arguments: None
##
## Return Value: None
##
sub DoMpathHash()
{
    my $tc = TimeCheck->new(STATEMENT => "DoMpathHash");
    LogMessage(LOG_DEBUG, 0, "***DoMpathHash - Enter") if ($gb_trace_function_calls);

    my $cmdout = `$gs_multipath_cmd -ll | grep mpath`;
    my $count = 0;

    # Loop through each line of the output, creating the hashes as we go
    foreach my $line (split("\n", $cmdout))
    {
        my ($mpath, $uuid, $dmname, $model) = split(/\s+/, $line);
        $uuid =~ s/(\(|\))//g; # Get rid of parentheses
        $gh_uuid_to_mpath{$uuid} = $mpath;
        $gh_dmdev_to_mpath{$dmname} = $mpath;
        $gh_uuid_to_dmdev{$uuid} = $dmname;

        $count++;
    }

    LogMessage(LOG_DEBUG, 0, "$count DM-Multipath device names added to mpath hash");
    LogMessage(LOG_DEBUG, 0, "***DoMpathHash - Leave") if ($gb_trace_function_calls);
    $tc->show();
}

##
## Populate the %gh_uuid_to_dev hash, which contains all sdXX devices on the
## node.  The key is the device UUID and the value is a space-separated list
## of sd[a-z]+ device names that have that UUID.
##
## Arguments: None
##
## Return Value: None
##
sub DoDevHash()
{
    my $tc = TimeCheck->new(STATEMENT => "DoDevHash");
    LogMessage(LOG_DEBUG, 0, "***DoDevHash - Enter") if ($gb_trace_function_calls);

    chomp(my $cmdout = `ls /dev/sd* | egrep sd[a-z]+\$`);

    # Loop through each sdXX device and get its UUID
    foreach my $sddev (split("\n", $cmdout))
    {
        my @device = split("/", $sddev);
        my $dev = $device[-1];
        chomp(my $uuid = `$gs_scsiid_cmd -gus /block/$dev`);

        # If there are multiple paths, this hash will contain a string like "sda sdaf" (both devices)
        $gh_uuid_to_dev{$uuid} =
            ((defined($gh_uuid_to_dev{$uuid})) ? $gh_uuid_to_dev{$uuid} . " $dev" : $dev);
    }

    LogMessage(LOG_DEBUG, 0, "***DoDevHash - Leave") if ($gb_trace_function_calls);
    $tc->show();
}

##
## Populate the %gh_htl_to_uuid hash, which contains all devices on the node.
## The key is the device UUID and the value is a the H:T:L representation of
## that device path.  Therefore, a given device UUID will generally show up
## twice in the list of values in this hash.
##
## Arguments: None
##
## Return Value: None
##
sub DoHTLHash()
{
    my $tc = TimeCheck->new(STATEMENT => "DoHTLHash");
    LogMessage(LOG_DEBUG, 0, "***DoHTLHash - Enter") if ($gb_trace_function_calls);

    chomp(my $cmdout = `$gs_lsscsi_cmd`);

    # Loop through each sdXX device and get its UUID
    for my $sddev (split("\n", $cmdout))
    {
        my @device = split(" ", $sddev);
        my $sddev = $device[-1];
        my $htl = $device[0];
        my @htl_fields = split(":", $htl);

        $htl = $htl_fields[0] . ":" . $htl_fields[2] . ":" . $htl_fields[3];

        @device = split("/", $sddev);
        my $dev = $device[-1];

        chomp(my $uuid = `$gs_scsiid_cmd -gus /block/$dev`);

        $gh_htl_to_uuid{$htl} = $uuid;
    }

    LogMessage(LOG_DEBUG, 0, "***DoHTLHash - Leave") if ($gb_trace_function_calls);
    $tc->show();
}

##
## Create a hash that contains SCSI block device name (e.g. sdb) as the key and
## the multipath status for that device as the value.
##
## Arguments: None
##
## Return Value: None
##
sub DoPathStatusHash()
{
    my $tc = TimeCheck->new(STATEMENT => "DoPathStatusHash");
    LogMessage(LOG_DEBUG, 0, "***DoPathStatusHash - Enter") if ($gb_trace_function_calls);

    chomp(my $cmdout = `$gs_multipath_cmd -ll`);

    for my $line (split("mpath", $cmdout))
    {
        my ($sd, $status) = ("", "");

        for my $mpathdev (split("\n", $line))
        {
            $sd = $1 if ($mpathdev =~ /(sd[a-z]+)/);
            $status = $1 if ($mpathdev =~ /(\[\w+\]\[\w+\])/);

            if ($sd && $status)
            {
                $gh_dev_path_state{$sd} = $status;
                ($sd, $status) = ("", "");
            }
        }
    }

    LogMessage(LOG_DEBUG, 0, "***DoPathStatusHash - Leave") if ($gb_trace_function_calls);
    $tc->show();
}

##
## Create a few hashes that associate various device attributes (e.g. UUID)
## with DM-Multipath device attributes (mpathXX name, dm-X name, etc).
## This greatly improves performance, as the lunmgr can then use these
## in-memory data structures rather than constantly running the 'multipath'
## command.
##
## Arguments: None
##
## Return Value: None
##
sub BuildDeviceHashes()
{
    my $tc = TimeCheck->new(STATEMENT => "BuildDeviceHashes");
    LogMessage(LOG_DEBUG, 0, "***BuildDeviceHashes - Enter") if ($gb_trace_function_calls);

    if (%gh_uuid_to_mpath || %gh_dmdev_to_mpath)
    {
        LogMessage(LOG_DEBUG, 0, "Multipath hashes already created, returning early");
    }

    else
    {
        my ($thr1, $thr2, $thr3);

        $thr1 = threads->create(\&DoMpathHash) unless ($gb_brief_option); # Send a thread off to do this work
        $thr2 = threads->create(\&DoDevHash) unless ($gb_brief_option);
        $thr3 = threads->create(\&DoPathStatusHash) unless ($gb_brief_option);

        LogMessage(LOG_DEBUG, 0, "Waiting for device hash threads to complete");

        DoHTLHash();

        $thr1->join() unless ($gb_brief_option);
        $thr2->join() unless ($gb_brief_option);
        $thr3->join() unless ($gb_brief_option);
    }

    LogMessage(LOG_DEBUG, 0, "***BuildDeviceHashes - Leave") if ($gb_trace_function_calls);
    $tc->show();
}

##
## Get the multipath device node name that this SCSI device is associated with.
##
## Arguments: One - required
##              @_[0] - Device UUID to check
##
## Return Value: The mpathXX name
##
sub GetMpathName($)
{
    my $dev = shift(@_);
    my ($dmdev, $slavedevs, $res);

    LogMessage(LOG_DEBUG, 0, "***GetMpathDevice - Enter") if ($gb_trace_function_calls);

    $res = $gh_uuid_to_mpath{$dev};

    LogMessage(LOG_DEBUG, 0, "***GetMpathDevice - Leave") if ($gb_trace_function_calls);

    return ($res);
}

##
## Turn on (or off) the LED for a disk associated with the SeaQuest volume name
## passed in on the command line.
##
## Arguments: None
##
## Return Value: Success or failure
##
sub SetLED()
{
    my $doslot;
    my $diskfound = 0;

    LogMessage(LOG_DEBUG, 0, "***SetLED - Enter") if ($gb_trace_function_calls);

    # Loop through device table devices
    ALLDEVS: foreach my $device (@ga_alldisks)
    {
        my $vdisk_wwn_nozeroes = $device->uuid();
        $vdisk_wwn_nozeroes =~ s/0//g;
        $vdisk_wwn_nozeroes =~ s/^36//g if ($vdisk_wwn_nozeroes =~ /^36[0-9a-f]+/);

        next if ($gs_current_cmd_opt ne ($device->mount_point . "/" . $device->name));

        ALLMSAS: foreach my $msa (@ga_allMSAs)
        {
            my $ports_ref = $msa->GetHostPortsStatus(); # For loop optimization

            foreach my $port (@{$ports_ref})
            {
                # Check if the current MSA is the one hosting this $device
                if (uc(${$port}{'port_wwn'}) eq uc($device->target_wwn))
                {
                    my $vdisks_ref = $msa->GetVdisks(1);
                    my $disks_ref = $msa->GetDisks();

                    for my $vdisk (@{$vdisks_ref})
                    {
                        my $volumes_ref = $vdisk->GetVolumes();

                        for my $vol (@{$volumes_ref})
                        {
                            if ($vdisk_wwn_nozeroes eq $vol->IDNoZero())
                            {
                                foreach my $disk (sort({$a->{'slot'} <=> $b->{'slot'}} @{$disks_ref}))
                                {
                                    # Try to cover the leftover disk state (removed from vdisk)
                                    if (${$disk}{'disk_state'} eq "LEFTOVR")
                                    {
                                        if ((${$disk}{'slot'} - 1) == $device->lun())
                                        {
                                            $doslot = ${$disk}{'slot'};
                                            $diskfound = 1;

                                            LogMessage(LOG_DEBUG, 0, "$gs_current_cmd_opt is on the disk in slot " . ${$disk}{'slot'} .
                                                    " on " . lc(${$port}{'port_wwn'}));

                                            if ($msa->SetLED($doslot, $gs_ledstate) == -1) # Change the UID LED state
                                            {
                                                LogMessage(LOG_ERR, 0, "Error setting LED $gs_ledstate: " .
                                                           $msa->GetLastError());
                                            }
                                        }
                                    }

                                    else
                                    {
                                        if ($vdisk->UUID() eq ${$disk}{'vdisk_id'})
                                        {
                                            $doslot = ${$disk}{'slot'};
                                            $diskfound = 1;

                                            LogMessage(LOG_DEBUG, 0, "$gs_current_cmd_opt is on the disk in slot " . ${$disk}{'slot'} .
                                                    " on " . lc(${$port}{'port_wwn'}));

                                            if ($msa->SetLED($doslot, $gs_ledstate) == -1) # Change the UID LED state
                                            {
                                                LogMessage(LOG_ERR, 0, "Error setting LED $gs_ledstate: " .
                                                        $msa->GetLastError());
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    $gi_exit_value = LUNMGR_DEVICE_NOT_FOUND if ($diskfound == 0);

    LogMessage(LOG_DEBUG, 0, "***SetLED - Leave") if ($gb_trace_function_calls);
}

##
##
##
sub DeviceStatus()
{
    my ($cmdout, $retcode, $device, $dev, $size, @size_fields, $cmd, $name_no_dsign);
    my ($hwver, $devs, @slaves, $uuid);
    my $numerrs = 0;
    my $STR_FMT = "%-32.32s %-6.6s %-9.9s %-16.16s %-6.6s %-5.5s\n";
    my $SIZE_FMT = "%-4d%s";
    my $partitioned = "Yes";

    LogMessage(LOG_DEBUG, 0, "***DeviceStatus - Enter") if ($gb_trace_function_calls);

    print "\n";
    print sprintf("$STR_FMT", "Logical Name", "Size", "Formatted", "Serial Number", "HW Ver", "SMART");
    print sprintf("$STR_FMT", DASHES, DASHES, DASHES, DASHES, DASHES, DASHES);

    BuildMSATwigs();

    # Loop through each volume from the command line
    foreach my $devck (split(",", $gs_current_cmd_opt))
    {
        ALLDEVS: foreach my $device (@ga_alldisks)
        {
            $name_no_dsign = $device->name;
            $name_no_dsign = substr($device->name, 1) if ($device->name =~ /^\$/);
            $uuid = $device->uuid;

            if (uc($devck) eq uc($name_no_dsign))
            {
                LogMessage(LOG_DEBUG, 0, "Retrieving status of " . $device->mount_point . "/" . uc($device->name));

                # Query the mpath device
                if (defined($gh_uuid_to_mpath{$device->uuid}))
                {
                    $dev = $gh_uuid_to_dmdev{$device->uuid};
                    chomp($size = `cat /sys/block/$dev/size`); # Number of 512-byte blocks
                    $size *= 512; # Size in bytes
                    $size = size_to_human_readable($size);
                    $devs = $gh_uuid_to_dev{$device->uuid};
                    @slaves = split(" ", $devs);
                    chomp($hwver = `cat /sys/block/$slaves[0]/device/rev`);
                    $dev = $gh_uuid_to_mpath{$device->uuid};
                    $cmdout = `$gs_fdisk_cmd -l /dev/mapper/$dev 2>&1`;

                    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
                    {
                        # TODO: handle this error
                    }

                    else
                    {
                        @size_fields = split("\n", $cmdout);

                        foreach (@size_fields)
                        {
                            if ($_ =~ /doesn't contain a valid partition table/)
                            {
                                $partitioned = "No";
                                last;
                            }
                        }
                    }

                    $cmdout = `$gs_smartctl_cmd --all -d scsi /dev/mapper/$dev 2> /dev/null`;

                    # smartctl only returns 0 when all is well with the disk
                    if (WIFEXITED($?) && (($retcode = WEXITSTATUS($?)) != 0))
                    {
                        # No SMART support (e.g. LUN behind an MSA controller)
                        if ($cmdout =~ /Device does not support SMART/)
                        {
                            my (@objects, @properties, @disk_twig_objs, @disk_properties);
                            my $health = "N/A";
                            my $disk_sn = "N/A";
                            my $vdisk_wwn_nozeroes = $device->uuid;
                            my $cnt = 0;

                            LogMessage(LOG_NOTICE, 0, "Device $dev does not support direct SMART inquries");

                            $vdisk_wwn_nozeroes =~ s/0//g;
                            $vdisk_wwn_nozeroes =~ s/^36//g if ($vdisk_wwn_nozeroes =~ /^36[0-9a-f]+/);

                            ALLMSAS: foreach my $msa (@ga_allMSAs)
                            {
                                my $ports_ref = $msa->GetHostPortsStatus();
                                my $volumes_ref = $msa->GetVolumes();
                                my $disks_ref = $msa->GetDisks();

                                foreach my $port (@{$ports_ref})
                                {
                                    # Check if the current MSA is the one hosting this $device
                                    if (uc(${$port}{'port_wwn'}) eq uc($device->target_wwn))
                                    {
                                        foreach my $vol (sort({$a->{'volume_name'} cmp $b->{'volume_name'}} @{$volumes_ref}))
                                        {
                                            if ($vdisk_wwn_nozeroes eq ${$vol}{'volume_id_no_zeroes'})
                                            {
                                                foreach my $disk (sort({$a->{'disk_slot'} <=> $b->{'disk_slot'}} @{$disks_ref}))
                                                {
                                                    if (${$vol}{'vdisk_id'} eq ${$disk}{'vdisk_id'})
                                                    {
                                                        $disk_sn = ${$disk}{'disk_serial_no'};
                                                        $health = ${$disk}{'disk_health'};
                                                        $hwver = ${$disk}{'revision'};
                                                        last ALLMSAS;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }

                            print sprintf("$STR_FMT", $device->mount_point . "/" . $device->name, $size, $partitioned, $disk_sn, $hwver, $health);
                        }

                        else
                        {
                            if ($retcode & oct("0b" . PARSE_FAILED))
                            {
                                $numerrs++;
                            }

                            if ($retcode & oct("0b" . OPEN_FAILED))
                            {
                                $numerrs++;
                            }

                            # We hit this on devices that don't support SMART (e.g. EVA LUN, etc)
                            # So it isn't necessarily an error per se
                            if ($retcode & oct("0b" . CMD_FAILED))
                            {
                                $numerrs++;
                            }

                            if ($retcode & oct("0b" . DISK_FAILING))
                            {
                                $numerrs++;
                            }

                            if ($retcode & oct("0b" . DISK_OK_PREFAIL))
                            {
                                $numerrs++;
                            }

                            if ($retcode & oct("0b" . DISK_OK_PAST_PREFAIL))
                            {
                                $numerrs++;
                            }

                            if ($retcode & oct("0b" . DEVICE_ERR_LOG_HAS_ERRS))
                            {
                                $numerrs++;
                            }

                            if ($retcode & oct("0b" . DEVICE_SELFTEST_LOG_HAS_ERRS))
                            {
                                $numerrs++;
                            }
                        }
                    }

                    # smartctl ran successfully to completion
                    else
                    {
                    }
                } # Device is in an mpath

                # Device not in an mpath - multipathing turned off, or only a single path
                else
                {
                    # TODO: HANDLE THIS CASE
                }
            }
        }
    }

    print "\n";
    LogMessage(LOG_DEBUG, 0, "***DeviceStatus - Leave") if ($gb_trace_function_calls);
}

##
## Mount a device in the table.  This command line option requires an additional
## parameter, the disk to mount.  This will be in the form of
## "/mount/point/$VOLUME."  The mount point is necessary to include since a
## primary and mirror disk have the same $VOLUME name, and the lunmgr would
## have no way to know which to mount if only the logical name was passed in.
## MountLun will try to mount the device using the DM-Multipath alias for
## failover/performance, but will fallback to mounting the standard SCSI device
## (/dev/sdX) if need be.
##
## Generally, if multipath devices aren't detected, it would mean that there
## aren't multiple paths out to the storage.  However, in the case that there
## are multiple paths but the mpath device nodes aren't created (e.g. the
## devices are blacklisted in multipath.conf or the multipath maps haven't been
## created yet), the lunmgr will simply mount the first /dev/sdX device
## associated with a given LUN.
##
## Arguments: None
##
## Return Value: LUNMGR_SUCCESS if successful, LUNMGR_MOUNT_ERROR otherwise
##
sub MountLun()
{
    my ($mntstr, $stderr, $mntdev, @devs);
    my $mntdriver = "mpath"; # How are we going to mount the device? assume mpath to start
    my $ret = 99;
    my $nummounts = 0; # Number of successful mounts
    my $NUMTRIES = 5;  # Allow five retries if it looks like the disk is mounted somewhere else
    my $i;

    LogMessage(LOG_DEBUG, 0, "***MountLun - Enter") if ($gb_trace_function_calls);

    chop($gs_current_cmd_opt) if (substr($gs_current_cmd_opt, -1, 1) eq "/"); # Remove trailing slash if it's there

    my @alldevs = split(",", $gs_current_cmd_opt);
    my $numdevs = scalar(@alldevs);

    for my $mntdev (@alldevs)
    {
        # Only non-root (i.e. SeaQuest userids) should have $MY_NODES set
        if (($< != 0) && (!$gb_skip_double_mount_ck))
        {
            my $instance_nodes = $ENV{'MY_NODES'};

            if (!defined($instance_nodes))
            {
                LogMessage(LOG_NOTICE, 0, "The environment variable \$MY_NODES must be " .
                        "defined in order to check for double mounts");
                $ret = 198;
                last;
            }

            my $cmd = "$gs_pdsh_cmd $instance_nodes mount | grep $mntdev";
            my $ismounted = 1;

            $cmd =~ s/\$/\\\$/g; # Escape the dollar sign

            # Check to make sure the disk isn't already mounted on a different node
            for ($i = 1; $i <= $NUMTRIES; $i++)
            {
                chomp(my $mntoutput = `$cmd`);

                if (($mntoutput eq "") || (WIFEXITED($?) && (WEXITSTATUS($?) != 0)))
                {
                    $ismounted = 0;
                    last;
                }

                LogMessage(LOG_INFO, 0, "$mntdev already mounted: \"$mntoutput\"");

                sleep(3);
            }

            # Disk is already mounted somewhwere else after 5 checks
            if ($ismounted)
            {
                $ret = 199;
                last;
            }
        }

        # SSD for hot tables mount type
        if ($gs_raidvol_name) {
            LogMessage(LOG_INFO, 0, "SSD/SRP mount request received");
            
            my ($local_mount, $remote_mount) = (0, 0);
            my $device_to_mount;
            
            if ($gb_remote_srp_mount) {
                chomp(my $scst_devs = `$gs_multipath_cmd -ll | grep SCST_BIO`);

                SCST_DEVS:
                for my $scstdev (split("\n", $scst_devs)) {
                    my @fields = split(' ', $scstdev);
                    my $mpath = $fields[0];
                    my $uuid = $fields[1];

                    $uuid =~ s/(\(|\))//g; # Get rid of parentheses
                    $uuid = "0" . $uuid;

                    LogMessage(LOG_INFO, 0, "SCST mpath device: $mpath/$uuid");

                    # Convert the UUID to ASCII to pull out hostname and volume name
                    $uuid =~ s/([a-fA-f0-9][a-fA-f0-9])/chr(hex($1))/eg;

                    if ($uuid =~ /_$gs_raidvol_name$/) {
                        $device_to_mount = "/dev/mapper/$mpath";
                        last SCST_DEVS;
                    }
                }

                LogMessage(LOG_INFO, 0, "Mounting " . $mntdev . " remotely over SRP [$device_to_mount]");
            }

            # "Primary" (local)
            else {
                $device_to_mount = "/dev/$gs_raidvol_name";

                LogMessage(LOG_INFO, 0, "Mounting " . $mntdev . " locally [$device_to_mount]");
            }

            my $mntstr = sprintf(
                "%s %s %s",
                $gs_mount_cmd,
                $device_to_mount,
                "\'$mntdev\'"
            );

            chomp(my $stderr = `$mntstr 2>&1 1>/dev/null`); # Capture only stderr

            # Mount failed
            if (WIFEXITED($?) && ((my $rc = WEXITSTATUS($?)) != 0)) {
                if (($rc == 32) && ($stderr =~ /already mounted on/)) {
                    $rc = $rc << 2; # Custom code (128) for device already mounted
                }

                if (($rc == 32) && ($stderr =~ /special device .+ does not exist/)) {
                    $rc = $rc << 3; # Custom code (256) for special device does not exist
                }

                LogMessage(LOG_ERR, 0, "Mount $mntdev ($device_to_mount) failed (" . mount_strerror($rc) . ")");

                $ret += 1;
            }

            # Successful mount
            else {
                LogMessage(LOG_INFO, 0, "$mntdev ($device_to_mount) successfully mounted");
                $nummounts++;
            }
        }

        else {
            ALLDEVS: for my $device (@ga_alldisks)
            {
                if (($device->mount_point . "/" . $device->name) eq $mntdev)
                {
                    # Mount using /dev/mapper/mpathXXp1
                    if (defined($gh_uuid_to_mpath{$device->uuid}))
                    {
                        $mntdev = $gh_uuid_to_mpath{$device->uuid};
                    }
    
                    # Mount using /dev/sdXX1 (e.g. no mpath device found)
                    else
                    {
                        # If below hash value isn't defined, we're screwed and can't mount
                        if (!defined($gh_uuid_to_dev{$device->uuid}))
                        {
                            $ret = LUNMGR_MOUNT_ERROR;
                            LogMessage(LOG_CRIT, 0, sprintf("Can't mount: no mpathXX or sdXX device found for %s", $device->uuid));
                            last; # Exit loop
                        }
    
                        $mntdev = $gh_uuid_to_dev{$device->uuid};
    
                        # In the case where DM-Multipath driver isn't managing this,
                        # volume, but there are actually multiple paths to the LUN,
                        # we just mount the first /dev/sd* device
                        @devs = split(/\s+/, $mntdev);
                        $mntdev = $devs[0];
                        $mntdriver = "sd";
    
                        LogMessage(LOG_WARNING, 0, sprintf("Mount device %s not managed by DM-Multipath", $device->uuid));
                    }
    
                    LogMessage(LOG_DEBUG, 0, "Mounting " . $device->name . " [" .
                        $device->uuid . ", " . $mntdev . "]");
    
                    $mntstr = sprintf("%s %s %s",
                        $gs_mount_cmd,
                        (($mntdriver eq "mpath") ? "/dev/mapper/${mntdev}p1" : "/dev/${mntdev}1"),
                        "'" . $device->mount_point . "/" . $device->name . "'"
                    );
    
                    $stderr = `$mntstr 2>&1 1>/dev/null`; # Capture only stderr
    
                    # Mount failed
                    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
                    {
                        my $rc = WEXITSTATUS($?);
                        $rc = $rc << 2 if (($rc == 32) && ($stderr =~ /already mounted on/)); # Custom code (128) for device already mounted
                        $rc = $rc << 3 if (($rc == 32) && ($stderr =~ /special device .+ does not exist/)); # Custom code (256) for special device does not exist
                        my $msg = "Mount " . $device->mount_point . "/" . $device->name . " ($mntdev) failed (" .
                                mount_strerror($rc) . ")";
    
                        LogMessage(LOG_ERR, 0, $msg);
    
                        $ret += 1;
                    }
    
                    # Successful mount
                    else
                    {
                        LogMessage(LOG_NOTICE, 0, $device->mount_point . "/" .
                                $device->name . " ($mntdev) successfully mounted");
                        $nummounts++;
                    }
    
                    last ALLDEVS; # Go to next device as specified on the command line (if any more)
                }
            }
        }
    }

    if ($nummounts != $numdevs)
    {
        LogMessage(LOG_ERR, 0, "One or more mounts failed");
        $ret = 1 if ($ret != 199);
    }

    else { $ret = 0; }

    LogMessage(LOG_ERR, 0, "Mount failed - No device for $gs_current_cmd_opt") if ($ret == 99);
    LogMessage(LOG_ERR, 0, "Mount failed - Attempted double mount for $gs_current_cmd_opt") if ($ret == 199);
    LogMessage(LOG_DEBUG, 0, "***MountLun - Leave") if ($gb_trace_function_calls);

    return ((defined($ret) && $ret == 0) ? LUNMGR_SUCCESS : LUNMGR_MOUNT_ERROR);
}

##
## For a mounted filesystem (that's busy) return an array of PIDs for the
## processes that are accessing the filesystem.  In addition to the PIDs, each
## element in the array will also contain the user name and process name.
##
## Arguments: One - required
##              @_[0] - Filesystem mount
##
## Return Value: Array containing all offending processes
##
sub CheckBusyPIDs($)
{
    my $fs = shift;
    my $cmd = sprintf("$gs_fuser_cmd -um '%s'", $fs);
    my @ret;

    LogMessage(LOG_DEBUG, 0, "***CheckBusyPIDs - Enter") if ($gb_trace_function_calls);

    chomp(my $output = `$cmd 2>&1`);

    # Error getting PIDs (process is gone, but files are still being closed?)
    if ((WIFEXITED($?) && (WEXITSTATUS($?) != 0)) || $output eq "")
    {
        LogMessage(LOG_NOTICE, 0, "Error retrieving processes using $fs: " . WEXITSTATUS($?));
    }

    else
    {
        # Loop through the output
        for my $line (split("\n", $output))
        {
            for my $field (split(/\s+/, $line))
            {
                if ($field =~ /^([0-9]+)[cefFrm]*\((.*)\)$/)
                {
                    chomp(my $exe = `$gs_ps_cmd -o ucmd= $1`);
                    push(@ret, "$1($2/$exe)");
                }
            }
        }
    }

    LogMessage(LOG_DEBUG, 0, "***CheckBusyPIDs - Leave") if ($gb_trace_function_calls);

    return (@ret);
}

##
## Unmount a disk associated with SQ logical volume name.
##
## Arguments: One - required
##              @_[0] - LUN to unmount, e.g. /database[m]/$VOLUME
##
## Return Value: LUNMGR_SUCCESS if successful, error otherwise
##
sub UnmountLun($)
{
    my $u_lun = shift(@_);
    my ($stderr, $cmd);
    my $err = LUNMGR_SUCCESS;

    LogMessage(LOG_DEBUG, 0, "***UnmountLun - Enter") if ($gb_trace_function_calls);

    $cmd = "$gs_umount_cmd \'$u_lun\'";
    $stderr = `$cmd 2>&1 1>/dev/null`;

    # Problem unmounting - what exactly was the problem?
    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
    {
        my $msg;

        $stderr = `echo '$stderr' | uniq`;
        $stderr =~ s/\n//g; # Trim any embedded and/or trailing newline

        # Device busy
        if ($stderr =~ /busy/)
        {
            $msg = "device is busy";
            $err = LUNMGR_UNMOUNT_ERROR_DEVICE_BUSY;
        }

        # Device not found
        elsif ($stderr =~ /not found/)
        {
            $msg = "device not found";
            $err = LUNMGR_UNMOUNT_ERROR_DEVICE_NOT_FOUND;
        }

        # Device not mounted
        elsif ($stderr =~ /not mounted/)
        {
            $msg = "device not mounted";
            $err = LUNMGR_UNMOUNT_ERROR_DEVICE_NOT_MOUNTED;
        }

        LogMessage(LOG_ERR, 0, "Unmount $u_lun failed ($msg)");

        # Why is the device busy?
        if ($err == LUNMGR_UNMOUNT_ERROR_DEVICE_BUSY)
        {
            my @pids = CheckBusyPIDs($u_lun);
            LogMessage(LOG_WARNING, 0, "Offending PIDs: " . join(" ", @pids));
            print STDERR "$u_lun busy - Offending PIDs: " . join(" ", @pids) . "\n"; # So the message will show up in sqmon.log at startup
        }
    }

    else { LogMessage(LOG_NOTICE, 0, "$u_lun successfully unmounted"); }

    LogMessage(LOG_DEBUG, 0, "***UnmountLun - Leave") if ($gb_trace_function_calls);

    return ($err);
}

##
## Unmount any SQ LUNs (belonging to the invoking user) that are mounted on
## the node.
##
## Arguments: None
##
## Return Value: LUNMGR_SUCCESS, unless a device is listed as mounted but we
##               get an error when trying to unmount it.  In this case, we
##               return LUNMGR_UNMOUNT_ERROR.  The log must be referenced if
##               we encounter errors, to see specific error types for any
##               particular filesystem unmount, since the final return code
##               will only be LUNMGR_UNMOUNT_ERROR.
##
sub UnmountAllLuns()
{
    my ($cmd, $stderr);
    my $errors = 0;
    my %my_disks; # Array containing "my" (e.g. calling userid) disks

    LogMessage(LOG_DEBUG, 0, "***UnmountAllLuns - Enter") if ($gb_trace_function_calls);

    # Being called under some userid from the Monitor
    if ($gs_mysqroot ne "")
    {
        # Build up array of disks associated with calling userid
        my $sqconfig_path = $gs_mysqroot . "/sql/scripts/sqconfig";
        my (@line, $volname, $volprim, $volmir);
        my $in_disk_section = 0;

        sysopen(SQCONFIG, $sqconfig_path, O_RDONLY) or $errors++;

        if ($errors == 0)
        {
            # Parse sqconfig
            while (<SQCONFIG>)
            {
                next if (/^#.*$/); # Could be comments in the section

                if (/^begin [htma]+se/)
                {
                    $in_disk_section = 1;
                    next;
                }

                if (/^end [htma]+se/) { $in_disk_section = 0; next; }

                if ($in_disk_section)
                {
                    chomp(@line = split(/\s+/, $_));

                    $volname = $line[0];

                    # Hot table TSE
                    if (scalar(@line) > 4) {
                        $volprim = $line[3];
                        $volmir = $line[4];
                    }

                    else {
                        $volprim = $line[2];
                        $volmir  = $line[3];
                    }

                    $my_disks{$volprim . "/" . $volname} = "mine";
                    $my_disks{$volmir . "/" . $volname} = "mine";
                }
            }

            close(SQCONFIG);
        }
    }

    # Either we were invoked as root (or as a userid that didn't have $MY_SQROOT set)
    # OR we successfully parsed our sqconfig to pick up the disks we're using
    if ($errors == 0)
    {
        if (%my_disks) {
            for my $lun (keys(%my_disks)) {
                $cmd = "grep '" . $lun . "' /proc/mounts";

                `$cmd > /dev/null 2>&1`;

                $errors += UnmountLun($lun) if ($? == 0);
            }
        }

        else
        {
            foreach my $device (@ga_alldisks)
            {
                $cmd = "grep '" . $device->mount_point . "/" . $device->name . "' /proc/mounts";
    
                if ((($gs_mysqroot ne "") && (defined($my_disks{$device->mount_point . "/" . $device->name}))) ||
                    ($gs_mysqroot eq ""))
                {
                    `$cmd > /dev/null 2>&1`;
    
                    # In this case, if the command exits with 0, it means device is listed as mounted
                    # Means we should only try to unmount if the device is actually mounted
                    $errors += UnmountLun($device->mount_point . "/" . $device->name) if ($? == 0);
                }
            }
        }
    }

    else { LogMessage(LOG_ERR, 0, "Error opening sqconfig; can't unmount all of this userid's devices"); }

    LogMessage(LOG_DEBUG, 0, "***UnmountAllLuns - Leave") if ($gb_trace_function_calls);

    return (($errors == 0) ? LUNMGR_SUCCESS : LUNMGR_UNMOUNT_ERROR);
}

##
## Remove a LUN from the system.  This is necessary, especially with the
## MSA2000 system when a disk goes bad (or something similar) and the LUN
## isn't removed automatically.
##
## Arguments: None
##
## Return Value: None
##
sub RemoveLun()
{
    my ($cmd, $stderr, $errors, @devtable_contents_lines);
    my $devno = 0; # Index into @ga_alldisks

    LogMessage(LOG_DEBUG, 0, "***RemoveLun - Enter") if ($gb_trace_function_calls);

    tie(@devtable_contents_lines, 'Tie::File', \*DEVTABLE) or
        die "Error tying into device table: $!";

    for my $device (@ga_alldisks)
    {
        if (($device->mount_point . "/" . $device->name) eq $gs_current_cmd_opt)
        {
            # Maybe the SCSI devices were already removed?
            if (defined($gh_uuid_to_dev{$device->uuid}))
            {
                # Loop through each sdX device for this LUN
                for my $sddev (split(/\s+/, $gh_uuid_to_dev{$device->uuid}))
                {
                    LogMessage(LOG_NOTICE, 0, sprintf("Removing %s from the system",
                        $sddev));

                    $cmd = "echo 1 > /sys/block/" . $sddev . "/device/delete";
                    $stderr = `$cmd 2>&1`;

                    # Use sysfs to remove the LUN
                    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
                    {
                        $stderr = `echo "$stderr" | uniq`;
                        $stderr =~ s/\n//g; # Trim any embedded and/or trailing newline
                        LogMessage(LOG_ERR, 0, "Remove failed [msg \"" .
                            "$stderr\"]");
                        $errors = 1;
                    }
                }
            }

            $device->uuid("XXX-REMOVED-XXX");
            LogMessage(LOG_NOTICE, 0, "$gs_current_cmd_opt marked as removed " .
                       "in device table (line " . ($devno + 1) . ")");
            $devtable_contents_lines[$devno] = $device->ToTableString();

            last;
        }

        $devno++;
    }

    untie(@devtable_contents_lines);

    LogMessage(LOG_DEBUG, 0, "***RemoveLun - Leave") if ($gb_trace_function_calls);
}

##
## Use QLogic rescan script to discover new LUNs on the connected HBAs.  Note
## that this will detect all new LUNs, not just a single new LUN (e.g.
## replacing a bad disk).
##
## Arguments: None
##
## Return Value: None
##
sub AddLuns()
{
    my ($ql_output, $hbtl, $hbtlold, $lsscsiout, @alldisks, @line, $cmd);
    my ($lun, @dev_fields, $scsiidout, $target, $target_wwn, @devtable_contents_lines);
    my $numluns = 0;
    my $devno = 0; # Index into @devtable_contents_lines

    LogMessage(LOG_DEBUG, 0, "***AddLuns - Enter") if ($gb_trace_function_calls);
    print "Discovering new LUNS... ";

    # Use the QLogic rescan script, which will discover all new LUNs
    chomp($ql_output = `$gs_qlrescan_cmd 2>/dev/null`);
    sleep(2); # Takes a bit of time to create device node

    print "done\nUpdating device table... ";

    tie(@devtable_contents_lines, 'Tie::File', \*DEVTABLE) or
        die "Error tying into device table: $!";

    chomp($lsscsiout = `$gs_lsscsi_cmd`);
    @alldisks = split("\n", $lsscsiout);

    # Loop through LUNs discovered
    for $hbtl (split("\n", $ql_output))
    {
        next if ($hbtl !~ /[0-9]+\:[0-9]+\:[0-9]+\:([0-9]+)/); # Skip until we see the found LUNs

        $lun = $1;
        chomp($hbtl);
        $hbtl =~ s/^\s+//; # Trim leading whitespace
        $hbtlold = $hbtl;
        $hbtl =~ s/:/\\:/g; # Escape the ':'
        @line = grep(/\[$hbtl\]/, @alldisks); # Only one match
        @line = split(/\s+/, $line[0]);
        @dev_fields = split("/", $line[-1]); # Split the /dev/sd* portion, get [dev][sd*] in the array

        $cmd = "$gs_scsiid_cmd -gus /block/" . $dev_fields[-1];
        chomp($scsiidout = `$cmd 2>/dev/null`);

        $cmd = "$gs_sg_inq_cmd -p 0x83 /dev/" . $dev_fields[-1];
        chomp($target = `$cmd`);
        $target =~ m/\[0x([0-9a-f]{16})\]/;
        $target_wwn = $1;

        LogMessage(LOG_INFO, 0, "New device: " . $dev_fields[-1] . " ($hbtlold)");

        # Fixup the removed entry in the device table
        for my $device (@ga_alldisks)
        {
            if ($device->target_wwn eq $target_wwn && $device->lun == $lun)
            {
                if ($device->uuid =~ /REMOVED/)
                {
                    $device->uuid($scsiidout);
                    LogMessage(LOG_NOTICE, 0, sprintf("Updating %s/%s [line %d] in the device table",
                        $device->mount_point,
                        $device->name,
                        $devno + 1));
                    $devtable_contents_lines[$devno] = $device->ToTableString();
                }
            }

            $devno++;
        }

        $devno = 0; # Reset the index counter
        $numluns++;
    }

    print "done\n";
    untie(@devtable_contents_lines);

    LogMessage(LOG_DEBUG, 0, "***AddLuns - Leave") if ($gb_trace_function_calls);
}

##
## Given a logical device name, identify the Linux name and utilize external
## tool to lay down a new filesystem on the device.  Note that the lunmgr
## itself is NOT doing the mkfs, it is merely relating logical name to
## physical name, and using that to call a separate utility.
##
sub MkfsHelper()
{
    my ($cmd, $output, $diskfound);
    my $ret = LUNMGR_SUCCESS;

    LogMessage(LOG_DEBUG, 0, "***MkfsHelper - Enter") if ($gb_trace_function_calls);

    # Look for the Linux device corresponding to the command line device
    for my $device (@ga_alldisks)
    {
        if (($device->mount_point() . "/" . $device->name()) eq $gs_current_cmd_opt)
        {
            if (defined($device->dev()) || defined($device->mpath()))
            {
                $diskfound = TRUE;
                $cmd = "$gs_pdsh_cmd -a 'mount' 2>/dev/null | grep '$gs_current_cmd_opt'";

                chomp($output = `$cmd`);

                # We don't want the grep to be successful - that would mean the disk is currently mounted
                if ($output eq "" && (WIFEXITED($?) && (WEXITSTATUS($?) != 0)))
                {
                    # Build command string: mpath name and EXT2 filesystem
                    $cmd = sprintf
                    (
                        "/opt/hp/nv/bin/remake_fs --device %s --fstype %s",
                        $device->mpath(),
                        "ext2"
                    );

                    LogMessage(LOG_INFO, 0, "Beginning filesystem reinit process (" .
                               $device->mpath() . "ext2)");

                    system($cmd);

                    # Some error
                    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0))
                    {
                        LogMessage(LOG_ERR, 1, "Error recreating filesystem, " .
                                   "see /tmp/fs_reinit.[stdout|stderr] for more information");
                        $ret = LUNMGR_MKFS_ERROR;
                    }
                }

                else
                {
                    LogMessage(LOG_WARNING, 0, "$gs_current_cmd_opt is already mounted, can't make new filesystem");

                    print STDERR "Error: Disk is currently mounted somewhere, won't make a new filesystem\n";
                    print STDERR "$output\n";

                    $ret = LUNMGR_MKFS_ERROR_CURRENTLY_MOUNTED;
                }
            }

            last;
        }
    }

    if (!$diskfound)
    {
        LogMessage(LOG_WARNING, 0, "$gs_current_cmd_opt not found on this node");
        print STDERR "$gs_current_cmd_opt not found on this node, please verify you are in the correct rack\n";
        $ret = LUNMGR_DEVICE_NOT_FOUND;
    }

    LogMessage(LOG_DEBUG, 0, "***MkfsHelper - Leave") if ($gb_trace_function_calls);

    return ($ret);
}

##
## Print the entire FC fabric topology (port WWNs of any connected storage
## boxes, device WWNs, etc) as seen from the FC switch nameserver.
##
## Arguments: Two - both required
##              @_[0] - Array containing the IP addresses for each JBOD
##                      management CLI.
##              @_[1] - Admin user password for the FC switch
##
## Return Value: None
##
sub ShowTopology()
{
#     my ($fcswitches, $fcpass) = @_;
    my $cmd;
    my $lv_lunSize;
    my $lv_scliTwig;
    my $lv_xmlRoot;

    LogMessage(LOG_DEBUG, 0, "***ShowTopology - Enter") if ($gb_trace_function_calls);

    # Dump to an XML file (for later user --sqcreate option)
    if ($gb_xml_option)
    {
        my $xmlfile = "/home/lunmgr/lunmgr.conf.xml";
        $xmlfile .= "." . $gs_current_cmd_opt if (defined($gs_current_cmd_opt));

        open(XMLOUT, ">", $xmlfile)
            or die "Couldn't open $xmlfile for writing: $!";
        print "Writing XML output to $xmlfile... ";
    }

    print XMLOUT "<?xml version=\"1.0\" ?>\n<lunmgr_conf>\n" if ($gb_xml_option);
    print XMLOUT "  <rack" . (defined($gs_current_cmd_opt) ? " number=\"$gs_current_cmd_opt\"" : "") . ">\n" if ($gb_xml_option);

    chomp(my $scli_output = `$gs_scli_cmd -t -x`);

    # Error with scli, can't print any topology information
    if ((WIFEXITED($?) && (WEXITSTATUS($?) != 0)) || $scli_output eq "")
    {
        LogMessage(LOG_INFO, 0, "Error executing $gs_scli_cmd");
        $gi_exit_value = LUNMGR_SCLI_ERROR;
        return;
    }

    $lv_scliTwig = XML::Twig->new();
    $lv_scliTwig->parse($scli_output);

    $lv_xmlRoot = $lv_scliTwig->root();

    # Each HBA port is an <HBA> element under the root
    for my $hba ($lv_xmlRoot->children('HBA'))
    {
        # Children of the <HBA> element are <HBA> and <Target>
        for my $hbainfo ($hba->children())
        {
            # <HBA> child
            if (uc($hbainfo->name()) eq "HBA")
            {
                my $wwnn = $hbainfo->att('WWNN');
                $wwnn =~ s/-//g; # Remove the dashes in the WWNN

                my $wwpn = $hbainfo->att('WWPN');
                $wwpn =~ s/-//g; # Remove the dashes in the WWPN

                my $portnum = $hbainfo->att('Port');

                # Write HBA port information to XML file
                if ($gb_xml_option)
                {
                    printf XMLOUT
                    (
                        "    <port number=\"%d\" wwnn=\"%s\" wwpn=\"%s\" src=\"%s\">\n",
                        $portnum,
                        lc($wwnn),
                        lc($wwpn),
                        hostname()
                    );
                }

                else { printf("\n%s\n%-36.36s", "Fibre Channel HBA port $portnum", DASHES); }
            }

            # <Target> child
            else
            {
                my $wwnn = $hbainfo->first_child('WWNN')->text();
                $wwnn =~ s/-//g; # Remove the dashes in the WWNN

                my $wwpn = $hbainfo->first_child('WWPN')->text();
                $wwpn =~ s/-//g; # Remove the dashes in the WWPN

                my $portid = $hbainfo->first_child('Info')->att('PortID');

                # Write disk enclosure information to the XML file
                if ($gb_xml_option)
                {
                    printf XMLOUT
                    (
                        "      <disk_enclosure wwnn=\"%s\" wwpn=\"%s\" port=\"%s\">\n",
                        lc($wwnn),
                        lc($wwpn),
                        $portid
                    );
                }

                else { print "\n  + " . lc($wwpn) . " with port ID $portid\n"; }

                # Get all the devices in this disk enclosure (match on WWPN)
                for my $device (@ga_alldisks)
                {
                    if (lc($wwpn) eq $device->target_wwn())
                    {
                        $cmd = "$gs_fdisk_cmd -l /dev/" . $device->dev();
                        $cmd = `$cmd 2>&1`;

                        if ($cmd =~ /([0-9]+\.[0-9]* GB),/)
                        {
                            $lv_lunSize = $1;

                            $lv_lunSize .= " (unpartitioned, unformatted)"
                                if ($cmd =~ /doesn't contain/);
                        }

                        # Write LUN information to the XML file
                        if ($gb_xml_option)
                        {
                            print XMLOUT "        <lun id=\"" . $device->lun() . "\">\n";
                            print XMLOUT "          <device>" . $device->uuid() . "</device>\n";
                            print XMLOUT "          <mpath>" . $device->mpath() . "</mpath>\n";
                            print XMLOUT "          <size>" . $lv_lunSize . "</size>\n";
                            print XMLOUT "          <mount_pt>" . $device->mount_point() . "</mount_pt>\n";
                            print XMLOUT "          <name>" . $device->name() ."</name>\n";
                            print XMLOUT "        </lun>\n";
                        }

                        else
                        {
                            print sprintf
                            (
                                "    - LUN: %d, /dev/%s [%s], %s\n",
                                $device->lun(),
                                $device->dev(),
                                $device->mpath(),
                                $lv_lunSize
                            );
                        }
                    }
                }

                print XMLOUT "      </disk_enclosure>\n" if ($gb_xml_option);
            }
        }

        print XMLOUT "    </port>\n" if ($gb_xml_option);
    }

    print XMLOUT "  </rack>\n" if ($gb_xml_option);
    print XMLOUT "</lunmgr_conf>\n" if ($gb_xml_option);
    print "done\n" if ($gb_xml_option);

    LogMessage(LOG_DEBUG, 0, "***ShowTopology - Leave") if ($gb_trace_function_calls);
}

##
## Open lunmgr.log.  Can be located under $MY_SQROOT/logs or
## /opt/hp/seaquest/release/latest/logs.
##
## Arguments: None
##
## Return Value: Result of the open() call
##
sub OpenLogFile()
{
    my ($oldumask, $logpath, $logerr, $firstwrite);
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

    $year += 1900;

    # We no longer want to have permissions of 0666, so let's leave the umask as is
    #$oldumask = umask(0); # Want 0666 for logfile
    $gs_logpath = (($gs_mysqroot ne "") ? $gs_mysqroot . "/logs/lunmgr.log" : "/var/log/lunmgr.log");
    $firstwrite = 1 if (!-e $gs_logpath); # We are creating the logfile
    $logerr = open(LMLOG, '>>', "$gs_logpath");

    # Successful open?
    if (!defined($logerr))
    {
        print STDERR "Error opening logfile $gs_logpath - no messages will be logged\n\n";
        $gs_nolog = 1;
    }

    else { flock(LMLOG, LOCK_EX); }

    print LMLOG "lunmgr $gs_version\n(C) Copyright 2008-$year Hewlett-Packard " .
                "Development Company, L.P.\n\n" if ($firstwrite && !$gs_nolog);

    print LMLOG "\n" if (!$firstwrite && !$gs_nolog);

    #umask($oldumask); # Restore previous umask

    return ($logerr);
}

##
## Event logging mechanism - All messages go to lunmgr.log (LOG_DEBUG only
## if debugging is turned on); messages with severity LOG_NOTICE or higher
## will go to the syslog.  A newline will be tacked onto the message when
## written to the log file.
##
## Arguments: Three - all required
##              @_[0] - priority of the message (LOG_EMERG, LOG_ALERT,
##                      LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO,
##                      LOG_DEBUG)
##              @_[1] - Override syslog logging (set to 1)
##              @_[2] - Message string (no newline)
##
## Return Value: None
##
sub LogMessage($$$)
{
    my ($priority, $syslog_override, $msg) = @_;

    return if ($gs_nolog); # Logfile isn't opened

    my @abbr = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
    my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
    $year += 1900;

    if (($priority == LOG_DEBUG && $gb_debug) || ($priority != LOG_DEBUG))
    {
        print LMLOG sprintf("%s %d %s %s %s: %s\n",
            $abbr[$mon],
            $mday,
            sprintf("%02d", $hour) . ":" . sprintf("%02d", $min) . ":" . sprintf("%02d", $sec),
            hostname(),
            basename($0),
            $msg
        )
    }

    # Anything LOG_ERR and more severe goes to syslog
    if ($priority <= LOG_ERR && !$syslog_override)
    {
        openlog("lunmgr", "ndelay,pid", LOG_USER);
        syslog($priority, $msg);
        closelog();
    }
}

##
## Wrapper for the standard Perl exit call, so we can also log what our exit
## status was.
##
## Arguments: One - optional
##              @_[0] - Exit code as defined in Globals.pm.  If this parameter
##                      is not specified, $gi_exit_value is used as exit code.
##
## Return Value: None
##
sub LMExit
{
    my $exit_code = $gi_exit_value;

    $exit_code = shift if (@_);

    LogMessage(LOG_INFO, 0, sprintf("Exiting %s ", strerror($exit_code)));
    close(LMLOG); # The lock will be released, after writing any buffered data
    nunflock($gs_logpath); # Release the log lock

    exit($gi_exit_value);
}


##
## Main
##
sub main()
{
    my ($cmd, $mpath, $logerr, $iconnect, @fcswitches, $disabled);

    my $tc = new TimeCheck();
    $disabled = InitGlobals();      # Initialize some globals
    $tc->show("InitGlobals");

    $logerr = OpenLogFile();         $tc->show("OpenLogFile");

    LogMessage(LOG_DEBUG, 0, "Running as userid $<");

    # Early bailout if on dev machine, or shared storage (not mountable)
    if ($disabled)
    {
        print STDERR "Notice: " . basename($0) . " is disabled, exiting early: @ga_Cmdline\n";
        LMExit(LUNMGR_SUCCESS);
    }

    $cmd = ParseCmdline();           $tc->show("ParseCmdline");
    $gi_current_cmd = $cmd;

    switch ($cmd)
    {
        # Device table creation, possibly using a previously generated XML
        # master config
        case (SQCREATE)
        {
            # Only allow root to create the device table
            if ($< != 0)
            {
                print STDERR "Only root may create the device table\n";
                LMExit(LUNMGR_INSUFFICIENT_PRIVILEGES);
            }

            print "Initializing... ";
            ParseConfig() if (defined($gs_master_config_loc));
            OpenDevtable(O_RDWR | O_CREAT | O_APPEND);
            BuildDeviceHashes();
            DiscoverLVM();
            print "done\n";
            FindFCDisks();
            FindSASDisks();
            WriteAllDevices();
            close(DEVTABLE);
            nunflock($gs_devtable_path);

            print "Number of devices discovered: " . Lunmgr::Device->NumDevs . "\n";
            print "Number of devices written: " . Lunmgr::Device->NumDevsWritten . "\n";
        }

        case (GETVERSION)
        {
            print basename($0) . " $gs_version\n";
        }

        case (HELP)
        {
            PrintUsage();
            LMExit(LUNMGR_SUCCESS);
        }

        case (PRINT)
        {
            OpenDevtable(O_RDONLY);
            ReadDevtable();
            $gi_exit_value = PrintAllDevices();
            close(DEVTABLE);
        }

        case (STATUS)
        {
            OpenDevtable(O_RDONLY);
            ReadDevtable();
            DeviceStatus();
            close(DEVTABLE);
        }

        case (LEDCMD)
        {
            OpenDevtable(O_RDONLY);
            ReadDevtable();
            SetLED();
            close(DEVTABLE);
        }

        case (MOUNT)
        {
            OpenDevtable(O_RDONLY);
            ReadDevtable();
            $gi_exit_value = MountLun();
            close(DEVTABLE);
        }

        case (UNMOUNT)
        {
            $gi_exit_value = UnmountLun($gs_current_cmd_opt);
        }

        case (UNMOUNTALL)
        {
            OpenDevtable(O_RDONLY);
            ReadDevtable();
            $gi_exit_value = UnmountAllLuns();
            close(DEVTABLE);
        }

        case (REMOVE)
        {
            OpenDevtable(O_RDWR);
            ReadDevtable();
            RemoveLun();
            close(DEVTABLE);
            nunflock($gs_devtable_path);
        }

        case (SQADD)
        {
            OpenDevtable(O_RDWR);
            ReadDevtable();
            AddLuns();
            close(DEVTABLE);
            nunflock($gs_devtable_path);
        }

        # Show the storage topology, either printing to STDOUT, or writing to
        # an XML file, for later use in device table creation
        case (LISTJBODS)
        {
            BuildDeviceHashes();
            FindFCDisks();
            DiscoverLVM();
            ShowTopology();
        }

        case (MKFS)
        {
            OpenDevtable(O_RDONLY);
            ReadDevtable();
            $gi_exit_value = MkfsHelper();
        }
    }

    LMExit();
}


##############################################################################
##############################################################################


main();
